#include "include.h"

/* Implemented Commands */
char *helpStr = "help";
char *listStr = "list";
char *infoStr = "info";
char *stopStr = "stop";
char *startStr = "start";
char *addStr = "add";
char *forceStr = "force";
char *exitStr = "exit";
char *exitStr2 = "quit";
char *exitStr3 = "q";

/* Functions that map to each command */
int cliHelp(cli_t *c, char *args[]);
int cliList(cli_t *c, char *args[]);
int cliInfo(cli_t *c, char *args[]);
int cliStop(cli_t *c, char *args[]);
int cliStart(cli_t *c, char *args[]);
int cliAdd(cli_t *c, char *args[]);
int cliForceAnnounce(cli_t *c, char *args[]);
int cliExit(cli_t *c, char *args[]);

cli_t *cliInit(client_t *c)
{
   cli_t *rcli = malloc(sizeof(cli_t));
   rcli->c = c;
   rcli->running = 0;
   rcli->torrentList = NULL;

   rcli->inf = stdin;
   rcli->outf = stdout;

   // Populate the commands dictionary
   rcli->commands = newDict((int(*)(const void *, const void *))strcmp);
   dictPut(rcli->commands, helpStr, cliHelp);
   dictPut(rcli->commands, listStr, cliList);
   dictPut(rcli->commands, infoStr, cliInfo);
   dictPut(rcli->commands, stopStr, cliStop);
   dictPut(rcli->commands, startStr, cliStart);
   dictPut(rcli->commands, addStr, cliAdd);
   dictPut(rcli->commands, forceStr, cliForceAnnounce);
   dictPut(rcli->commands, exitStr, cliExit);
   dictPut(rcli->commands, exitStr2, cliExit);
   dictPut(rcli->commands, exitStr3, cliExit);
   return rcli;
}

clierror_type cliDoCommand(cli_t *cli, char *command, char *args[])
{
   loggerLog(LOG_INFO, "User entered command %s with args:\n", command);
   char **curr = args;
   while(*curr)
      loggerLog(LOG_INFO, "%s ", *(curr++));
   loggerLog(LOG_INFO, "\n");

   clierror_type ret = CLI_NOERROR;
   // Does the command exist?
   if(dictContainsKey(cli->commands, command))
   {  // Call the appropriate function
      int (*comm) (cli_t *, char **);
      comm = dictGet(cli->commands, command);
      if(comm(cli, args)) // Report if an error occurred
         ret = CLI_COMMANDERROR;
   }
   else // Nope, you dun-goofeed, user.
      ret = CLI_NOSUCHCOMMAND;
   return ret;
}

void cliRun(cli_t *cli)
{
   FILE *inf = cli->inf;
   FILE *outf = cli->outf;

   char *line;
   int len;

   char *command;
   char *args[11];
   int i;
   clierror_type err;
   cli->running = 1;
   fprintf(stdout, "\n");
   do
   {  // Get user input
      fprintf(cli->outf, "? >> ");
      line = NULL;
      getline(&line, (size_t *) &len, inf);
      // Parse input into str array
      for(i = 0; i < 11; i++) args[i] = NULL;
      // Get the command
      command = strtok(line, " \n\t");
      if(command == NULL) continue;
      // Get the args (10 maximum)
      for(i = 0; i < 10; i++)
         args[i] = strtok(NULL, " \n\t");
      err = cliDoCommand(cli, command, args);
      if(!cli->running)
         break;
      switch(err)
      {
         case CLI_COMMANDERROR:
            fprintf(outf, "Error: Failed to do \"%s\".\n", command);
            break;
         case CLI_NOSUCHCOMMAND:
            fprintf(outf, "Error: No such command \"%s\" exists.\n", command);
            break;
         default:
            break;
      }
      free(line);
   }
   while(cli->running);
}


int cliHelp(cli_t *c, char *args[])
{
   FILE *outf = c->outf;
   fprintf(outf, "Available commands:\n");
   fprintf(outf, "  %s\n", helpStr);
   fprintf(outf, "  %s\n", listStr);
   fprintf(outf, "  %-7s [torrent number]\n", infoStr);
   fprintf(outf, "  %-7s [torrent number]\n", stopStr);
   fprintf(outf, "  %-7s [torrent number]\n", startStr);
   fprintf(outf, "  %-7s [path/to/torrent]\n", addStr);
   fprintf(outf, "  %-7s [torrent number]\n", exitStr);
   fprintf(outf, "\n");

   return 0;
}

int cliList(cli_t *c, char *args[])
{
   FILE *outf = c->outf;

   client_t *cl = c->c;
   list_t *torrs = cl->torrents;
   listLock(torrs);

   int i = 0;
   int numTorrents = listGetSize(torrs);
   fprintf(outf, "There are %d available torrents:\n", numTorrents);

   // Construct torrent list in CLI.
   if(c->torrentList != NULL)
      free(c->torrentList);
   c->torrentList = malloc(sizeof(torrent_t *) * numTorrents);
   c->tListSize = numTorrents;

   torrent_t *curr;
   char *cFilename;
   int cFilenameLen;

   // Iterate through torrents
   resetList(torrs);
   while(!endList(torrs))
   {
      curr = getObject(torrs);
      c->torrentList[i++] = curr;
      cFilename = torrentGetName(curr);
      cFilenameLen = strlen(cFilename);
      fprintf(outf, "  %d: %.35s", i, cFilename);
      if(cFilenameLen > 35)
         fprintf(outf, "...");
      fprintf(outf, "\n");
   }
   listRelease(torrs);

   return 0;
}

int cliInfo(cli_t *c, char *args[])
{
   FILE *outf = c->outf;

   // If user hasn't even looked at the torrent list, they don't know what
   // they're requesting...
   if(c->torrentList == NULL) return 0;

   int n;
   for(n = 0; n < 10; n++) // Determine number of args
      if(args[n] == NULL) break;

   // Don't do anything if there aren't any args
   if(n == 0) return 0;

   // Print torrent info for all requested torrents
   int i;
   int requested;
   torrent_t *t;
   char *cFilename;
   int cFilenameLen;
   char *status;

   int connectedPeers;
   int totalPeers;
   for(i = 0; i < n; i++)
   {
      // Convert from 1-base to 0-base
      requested = atoi(args[i]) - 1;

      // Bounds check
      if(requested < 0 || requested >= c->tListSize)
      {
         fprintf(outf, "Requested torrent %d does not exist.\n", requested + 1);
         continue;
      }

      t = c->torrentList[requested];

      /* Print information about t */
      // Name
      cFilename = torrentGetName(t);
      cFilenameLen = strlen(cFilename);
      fprintf(outf, "%.35s", cFilename);
      if(cFilenameLen > 35)
         fprintf(outf, "...");
      fprintf(outf, "\n");

      // Status
      status = torrentStatusString(t);
      fprintf(outf, "  Status:     %s\n", status);
      free(status);
      // Downloaded
      fprintf(outf, "  Downloaded: %2.2lf %%\n", torrentDownloaded(t));

      // Peers
      connectedPeers = listGetSize(t->actpeers);
      totalPeers = t->totalSeeders + t->totalLeechers;
      fprintf(outf, "  Connected Peers: %d/%d\n", connectedPeers, totalPeers);
   }

   return 0;
}

int cliStop(cli_t *c, char *args[])
{
    FILE *outf = c->outf;

   // If user hasn't even looked at the torrent list, they don't know what
   // they're requesting...
   if(c->torrentList == NULL) return 0;

   int n;
   for(n = 0; n < 10; n++) // Determine number of args
      if(args[n] == NULL) break;

   // Don't do anything if there aren't any args
   if(n == 0) return 0;

   // Print torrent info for all requested torrents
   int i;
   int requested;
   torrent_t *t;
   char *cFilename;
   int cFilenameLen;

   for(i = 0; i < n; i++)
   {
      // Convert from 1-base to 0-base
      requested = atoi(args[i]) - 1;

      // Bounds check
      if(requested < 0 || requested >= c->tListSize)
      {
         fprintf(outf, "Requested torrent %d does not exist.\n", requested + 1);
         continue;
      }
      t = c->torrentList[requested];


      // Name
      cFilename = torrentGetName(t);
      cFilenameLen = strlen(cFilename);

      fprintf(outf, "Stopping %.35s", cFilename);
      if(cFilenameLen > 35)
         fprintf(outf, "...");
      fprintf(outf, "\t");

      if(t->status == STAT_STOPPED)
         fprintf(outf, "Already ");
      t->status = STAT_STOPPING;
      fprintf(outf, "Stopped\n");
   }
   
   return 0;
}

int cliStart(cli_t *c, char *args[])
{
    FILE *outf = c->outf;

   // If user hasn't even looked at the torrent list, they don't know what
   // they're requesting...
   if(c->torrentList == NULL) return 0;

   int n;
   for(n = 0; n < 10; n++) // Determine number of args
      if(args[n] == NULL) break;

   // Don't do anything if there aren't any args
   if(n == 0) return 0;

   // Print torrent info for all requested torrents
   int i;
   int requested;
   torrent_t *t;
   char *cFilename;
   int cFilenameLen;

   for(i = 0; i < n; i++)
   {
      // Convert from 1-base to 0-base
      requested = atoi(args[i]) - 1;

      // Bounds check
      if(requested < 0 || requested >= c->tListSize)
      {
         fprintf(outf, "Requested torrent %d does not exist.\n", requested + 1);
         continue;
      }
      t = c->torrentList[requested];


      // Name
      cFilename = torrentGetName(t);
      cFilenameLen = strlen(cFilename);

      fprintf(outf, "Starting %.35s", cFilename);
      if(cFilenameLen > 35)
         fprintf(outf, "...");
      fprintf(outf, "\t");

      if(t->status == STAT_SEEDING || t->status == STAT_LEECHING)
         fprintf(outf, "Already ");
      t->status = STAT_STARTING;
      fprintf(outf, "Started\n");
   }
   
   return 0;
}

int cliAdd(cli_t *c, char *args[])
{
   if(args[0] == NULL)
      return 0;
   if(clientAddTorrentFile(c->c, args[0]) == 0)
      return 1;
   return 0;
}

int cliForceAnnounce(cli_t *c, char *args[])
{
   FILE *outf = c->outf;

   // If user hasn't even looked at the torrent list, they don't know what
   // they're requesting...
   if(c->torrentList == NULL) return 0;

   char *peer_id = c->c->peer_id;
   int port = c->c->port;

   int n;
   for(n = 0; n < 10; n++) // Determine number of args
      if(args[n] == NULL) break;

   // Don't do anything if there aren't any args
   if(n == 0) return 0;

   // Print torrent info for all requested torrents
   int i;
   int requested;
   torrent_t *t;
   char *cFilename;
   int cFilenameLen;

   for(i = 0; i < n; i++)
   {
      // Convert from 1-base to 0-base
      requested = atoi(args[i]) - 1;

      // Bounds check
      if(requested < 0 || requested >= c->tListSize)
      {
         fprintf(outf, "Requested torrent %d does not exist.\n", requested + 1);
         continue;
      }
      t = c->torrentList[requested];


      // Name
      cFilename = torrentGetName(t);
      cFilenameLen = strlen(cFilename);

      fprintf(outf, "Attempting to force-announce %.35s", cFilename);
      if(cFilenameLen > 35)
         fprintf(outf, "...");
      fprintf(outf, "\n");

      // Try to force-announce.
      if(!torrentTrackerAnnounce(t, peer_id, port))
         fprintf(outf, "Error reaching tracker for force-announce.\n");
      else
         fprintf(outf, "Force-announce was successful.\n");
      
   }
   return 0;
}

int cliExit(cli_t *c, char *args[])
{
   char *line = NULL;
   int len;
   fprintf(c->outf, "Are you sure you want to exit? [y/n] ");
   getline(&line, (size_t *) &len, c->inf);
   if(strcmp(line, "y\n") == 0 || strcmp(line, "yes\n") == 0)
   {
      fprintf(c->outf, "Exiting...\n");
      c->running = 0;
   }

   return 0;
}

int cliShutdown(cli_t *cli)
{
   cli->running = 0;
   free(cli->torrentList);
   dictClear(cli->commands);
   free(cli->commands);
   return 0;
}

